package haxe.async;
import haxe.macro.Map;
import haxe.macro.Context;
import haxe.macro.Expr;
import haxe.macro.Type;

/**
 * ...
 * @author waneck
 */

typedef YieldResult<T> = 
{
	var __selfref__(default, null):YieldResult<T>;
	
	//all logic will unfortunately be here. It would be much better to have Enumerator logic.
	function hasNext():Bool;
	function next():T;
	
	//will release to the gc the possible references it holds
	function dispose() : Void;
	
}

class Yield extends Map
{
	@:macro public static function make(e:Expr):Expr
	{
		var y = new Yield(e);
		trace(e);
		return { expr:EConst(CInt("10")), pos:e.pos };
		//return y.run();
	}
	
	public static inline function getIterator<T>(d:Dynamic):Iterator<T>
	{
		if (Reflect.hasField(d, "iterator"))
			return d.iterator();
		else
			return d;
	}
	
	#if macro
	var expr:Expr;
	
	//current state number
	var state:Int;
	//a stack of try states
	var try_states:Array<Position>;
	
	var stack_var_remap:Array<Hash<String>>;
	var temp_var_count:Int;
	
	var statesContents:Array<Expr>;
	
	var hasYields:CachedPatternMatcher;
	
	var blocksToProcess:{slicedBlockContent:Array<Expr>, slicedBlockPosition:Position, whenFinishedGotoEndOf:Position};
	
	/////////// TRAVERSER VARIABLES
	//this will set to true when a yield is reached. We'll use that
	//to create a new state after the yield (so it can be executed asynchronously)
	var reachedYield:Bool;
	
	function new(expr) 
	{
		this.expr = expr;
		state = -1;
		try_states = [];
		stack_var_remap = [new Hash()];
		temp_var_count = 0;
		
		hasYields = new CachedPatternMatcher(this);
	}
	
	public function run()
	{
		return expr;
	}
	
	override function mapImpl(e:Expr) : Null<Expr>
	{
		
		return switch(e.expr)
		{
			case EConst(c):
				switch(c)
				{
					case CIdent(i):
						changeVar(i);
					default:
						e;
				}
			case EFunction(f):
				//if there is a closure, we do not want to replicate the Yield behavior there.
				//but we do want to remap the variables it's using
				new MapNames(this).run(e);
			case EVars(vars):
				for (v in vars)
					addVar(v.name);
				
				//let map normally parse its exprs
				null;
			case EBlock(b):
				
				//if this block contains a return (yield)
				if (this.hasYields.matchesPattern(e))
				{
					
					
				}
				
				null;
			
			default:
				super.mapImpl(e);
		}
		
	}
	
	function changeVar(s:String)
	{
		if (!hasVar(s))
		{
			//let's hope that the context will take care of disambiguation
			return mk(EConst(CIdent(s)));
		}
		
		return mk(EConst(CIdent(getRemap(s))));
		
	}
	
	function hasVar(s:String)
	{
		var len = stack_var_remap.length;
		while (len-- > 0)
		{
			if (stack_var_remap[len].exists(s))
				return true;
		}
		return false;
	}
	
	function addVar(s:String) : String
	{
		var s2 = "";
		if (hasVar(s))
		{
			//if nobody is going to see it, it doesn't have to be pretty
			stack_var_remap[stack_var_remap.length-1].set(s,s2 = "__temp__" + s +"_" + temp_var_count++);
		} else {
			stack_var_remap[stack_var_remap.length-1].set(s, s2= s);
		}
		
		return s2;
	}
	
	
	function getRemap(s:String)
	{
		var len = stack_var_remap.length;
		while (len-- > 0)
		{
			if (stack_var_remap[len].exists(s))
				return stack_var_remap[len].get(s);
		}
		return null;
	}
	
	
	
	#end
}

/**
*  CachedPatternMatcher will match either for returns, or our new <- operator.
*  if we are to insert new operators, this is the place to do it.
**/
private class CachedPatternMatcher extends Map
{
	#if macro
	var y:Yield;
	var matches:Bool;
	var cacheResult:Hash<Bool>;
	var cacheMappedExpr:Hash<Expr>;
	
	public function new(y) : Void
	{
		this.cacheResult = new Hash();
		cacheMappedExpr = new Hash();
		this.y = y;
	}
	
	//obviously not thread safe
	public function matchesPattern(e:Expr) : Bool
	{
		matches = false;
		
		var pos_s = Std.string(e.pos);
		if (cacheResult.exists(pos_s))
			return cacheResult.get(pos_s);
		
		map(e);
		
		var m = matches;
		matches = false;
		return m;
	}
	
	public function getCachedOrRun(e:Expr) : Expr
	{
		var pos_s = Std.string(e.pos);
		if (cacheMappedExpr.exists(pos_s))
			return cacheMappedExpr.get(pos_s);
		else
			return map(e);
	}
	
	
	override function mapImpl(e:Expr)
	{
		//if found something that matches, do not continue (might give out incorrect values otherwise)
		if (matches)
			return e;
		
		switch(e.expr)
		{
			case EReturn(eopt):
				matches = true;
				return e;
			
			case EFunction(_):
				//skip EFunction
				return e;
			
			case EBlock(b):
				var poss = Std.string(e.pos);
				
				//do not process again - we have it cached
				if (cacheResult.exists(poss))
					return e;
				else
					return null;
			default:
				return null;
		}
	}
	
	override function popStack() : Void
	{
		var pos = this.currentBlock.pos;
		var poss = Std.string(pos);
		
		cacheMappedExpr.set(poss, {expr: EBlock(this.currentMappedBlock), pos:pos});
		cacheResult.set(poss, matches);
	}
	
	
	/*
	 * Okay, we need some new operators that will do some cool things:
	 * - since there is no real standard way to do async programming in haXe, it would be nice to
	 * support the way haXe remoting and async proxy works:
	 * 	e.g.
	 * 
	 * myAsyncCall <= cnx.server.getComplexAction.call([]);
	 * 
	 * can be converted into:
	 * 
	 * var myAsyncCall;
	 * 
	 * cnx.server.getComplexAction.call([], Yield.Wait(__selfref__, function(v) myAsyncCall = v);
	 * return Yield.stop;
	 * 
	 * pros is that this will be compatible with haXe remoting, the con is that you can't e.g. cancel it.
	 * So a dispose() function won't be able to do much there, for example.
	 * 
	 * Also cool is that we can use try/catch block that actually calls setErrorHandler
	 * - Maybe supporting stax's Future<> would be the 'right' way to do it.
	 * 
	 * myAsyncCall <- server.processAsync(); //Returns Future<Something>
	 * 
	 * The bad part about this is that error handling or cancelling are dealt the same way
	 * 
	 * - Also it would be great to have some kind of type checking going on here:
	 * 
	 * e.g.
	 * 
	 * maybeAsyncOrSyncDependingOnPlatform <= cnx.server.getComplexAction([]);
	 * 
	 * will be type-checked and depending on the platform, it will be handled either by the case above, either by:
	 * 
	 * maybeAsyncOrSyncDependingOnPlatform = cnx.server.getComplexAction([]);
	 * return null; //in order to work the same way as the async version
	 */
	
	/**
	*  TODO
	* Aync Assign Op will convert an expression like this:
	*  a <- x(1,2,3);
	*  
	*  into:
	*  
	*  x(1,2,3, function(res) __solver__.resume(__selfref__));
	*  return Yield.stop;
	**
	function handleAsyncAssignOp(e1:Expr, e2:Expr, pos:Position) : Expr
	{
		//NOT IMPLEMENTED YET
		//return {expr: ,pos:pos}
		return null;
	}*/
	
	
	#end
}

/**
* Map closures' var names so they capture the correct variable. Check to see if they had been declared inside the actual closure
**/
private class MapNames extends Map
{
	#if macro
	var y:Yield;
	var varDeclStack:Array<Hash<Bool>>;
	
	public function new(y) : Void
	{
		this.y = y;
		varDeclStack = [new Hash()];
	}
	
	public function run(e) : Expr
	{
		return map(e);
	}
	
	
	override function pushStack() : Void
	{
		varDeclStack.push(new Hash());
	}
	
	override function popStack() : Void
	{
		varDeclStack.pop();
	}
	
	function find(v:String) : Bool
	{
		for (decl in varDeclStack)
			if (decl.exists(v))
				return true;
		
		return false;
	}
	
	function add(v:String) : Void
	{
		varDeclStack[varDeclStack.length-1].set(v, true);
	}
	
	override function mapImpl(e:Expr)
	{
		return switch(e.expr)
		{
			case EConst(c):
				switch(c)
				{
					case CIdent(c):
						if (!find(c))
							e;
						else
							untyped y.changeVar(c);
					default:
						e;
				}
			case EVars(vars):
				for (v in vars)
					add(v.name);
				
				null;
			default:
				null;
		}
	}
	
	#end
}